iT邦幫忙

2024 iThome 鐵人賽

DAY 16
1
JavaScript

TypeScript 初學者也能看的學習指南系列 第 16

TypeScript 初學者也能看的學習指南 16 - unknown 未知型別

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240925/20149362f3MR88c1BQ.png

unknown 和 any 經常放在一起比較,它們十分相似,但相較於 any, unknown 更為安全,因為它不像 any 一樣任何型別都可以接受,甚至操作不存在的屬性。那究竟在什麼情況下可以使用 unknown 呢?它可以接受什麼型別呢?

本篇將來介紹 unknown 和其使用時機,並會跟 any 一起做對照,在此也把之前介紹 any 的文章放在下方

🔗 文章傳送門:Day15 - any 任意型別

unknown

unknown 在 TypeScript 3.0 版本時被引入,被作為 any 更安全的替代,所謂的更安全是指被定義為 unknown 的變數、參數,在進行任何近一步的操作前都會經過型別檢查機制,相反地 any 則不需要

https://ithelp.ithome.com.tw/upload/images/20240807/20149362dFjP1ggjio.png
圖片來源

這張圖說明了 TypeScript 中不同型別之間的可指派性(assignability),請先專注看 unknown 和 any
每列(row)表示來源型別,每行(column)表示目標型別。表格的每一格表示是否可以將「來源型別」指派派給「目標型別」

藍色勾勾(✓):來源型別可以被指派給目標型別
綠色勾勾(✓):tsconfig.json 中的 strictNullChecks 關閉時才能被指派
紅色叉叉(✗):來源型別不能指派給目標型別

總結上圖,我們可以得知:

  1. any 和 unknown 都可以接受任何型別的值
  2. any 可以指派給任何型別,任何型別也可以指派給 any
  3. unknown 可以被指派給自己和 any。不能直接指派給其他具體型別(如 object、void 等),除非透過型別斷言或型別守衛
  4. unknown 是 nenver 的相反,never 可以被指派給任何型別,但沒有任何型別可以指派給 never

若看不懂沒有關係,透過範例會較好理解

使用時機

  1. 當不確定資料的型別時
    當你從外部來源(如 API 呼叫、第三方函式庫等)接收資料時,型別可能是不確定的。這時使用 unknown 可以安全地接收任何型別的資料

  2. 作為 any 的替代
    unknown 是相較於 any 更安全的替代品。any 雖然很靈活,但它會繞過 TypeScript 的型別系統,可能導致相關的錯誤產生

範例

透過以下幾個範例,來了解 unknown 的特性

  1. unknown 可以接受任何型別的值(any 也是)
let a: any;
let u: unknown;

// ✅ 以下都是 Pass
a = 10;           
a = 'Hello';      
a = true;        
a = { key: 'value' }; 

u = 10;          
u = 'Hello';     
u = true;         
u = { key: 'value' };  
  1. unknown 不能直接對值進行操作
function f1(v1: any) {
  v1.b(); // ✅ Pass
}
function f2(v2: unknown) {
  v2.b();      // ❌ 'a' is of type 'unknown'.
  v2.foo.bar;  // 同上
  v2.trim();   // 同上
  v2();        // 同上
  new v2();    // 同上
  v2[1];       // 同上
}
  1. unknown 能指派給 any 或它自己,但不能直接指派給其他型別,除非進行型別斷言或型別守衛
let value1: unknown;
let value2: any = value1; // ✅ Pass,unknown 可以指派給 any
let value3: unknown = value1; // ✅ Pass,unknown 可以指派給 unknown

let str: string;
str = value1;   // ❌ unknown 不能直接指派給字串,Type 'unknown' is not assignable to type 'string'.
let num: number = value1;   // ❌ unknown 不能直接指派給數字,Type 'unknown' is not assignable to type 'number'.
let booleanVal: boolean = value1; // ❌ ...
let objectVal: object = value1; // ❌ ...
let arr: any[] = value1; // ❌ ...
let f1: Function = value1; // ❌ ...


// 需要進行型別斷言或型別守衛才能賦值給其他型別
if (typeof value1 === 'string') {
  str = value1;     // ✅ Pass,已經檢查過是字串
}

限縮(Narrowing)unknown 的範圍

在對 unknown 變數進行任何操作前,需要先透過型別守衛 Type Guards 進行 Narrowing,如使用 typeof、instanceof 、Type Predicate (型別謂詞)等,確定具體型別是什麼,後續的操作才有意義

  1. typeof
let value: unknown;

if (typeof value === 'string') {
  console.log(value.toUpperCase()); // ✅ Pass
}
  1. instanceof
class Person {
  constructor(public name: string) {
    // ... 略 ...
  };
  sayHello() {
    return `Hello ${this.name}`;
  };
}

class Animal {
  constructor(public species: string) {
    // ... 略 ...
  };
  makeSound() {
    return `${this.species} makes a sound`;
  };
}

function greet(value: unknown) {
  if (value instanceof Person) { // ✅ Pass
    console.log(value.sayHello());
  } else if (value instanceof Animal) { // ✅ Pass
    console.log(value.makeSound());
  } else {
    console.log('Unknown type');
  }
}

const john = new Person('John');
const dog = new Animal('Dog');

greet(john);  // Hello John
greet(dog);   // Dog makes a sound
greet(123);   // Unknown type
  1. Type Predicate (型別謂詞)
    Type Predicate (型別謂詞) 是屬於自定義型別守衛 (User-Defined Type Guards)的一種
    經常用在一個函式的回傳值,語法結構由【 parameterName is Type】組成,parameterName 是函式中的參數名稱,Type 就是你定義的型別

下方範例中的 animal is Dog 就是 Type Predicate

type Animals = {
  type: string;
};

type Dog = Animals & {
  bark: () => void;
};

function isDog(animal: unknown): animal is Dog {
  return (animal as Animals).type === 'dog';
}

console.log(isDog({ type: 'dog' }));  // true
console.log(isDog({ type: 'human' }));  // false

型別斷言(Type Assertions) + unknown

const unknownVal: unknown = 1;
const numVal: number = unknownVal as number;
console.log(numVal + 1); // ✅ Pass, 2
console.log(unknownVal + 1) // ❌ 'value1' is of type 'unknown'.

總結

unknown any
有型別檢查 跳過型別檢查
可以接受任何型別 可以接受任何型別的值
只能被指派給 unknown 或 any,或需要通過型別斷言後指派給其他型別 可以指派給任何型別
在未進行型別守衛或型別斷言前,不能對變數進行任何操作 可以對變數進行任意操作,沒有限制

💡 若版本允許,使用 unknown 會比 any 更為安全唷

每天的內容有推到 github 上喔

References


上一篇
TypeScript 初學者也能看的學習指南 15 - any 任意型別
系列文
TypeScript 初學者也能看的學習指南16
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言